using System;
using System.Collections;
using System.Collections.Generic;
using TestCases.CompareAssert.Core.Comparers.Interfaces;
using TestCases.CompareAssert.Core.Interfaces;
using TestCases.Utils;

namespace TestCases.CompareAssert.Core.Comparers.Collections
{
    public class CollectionsComparer: CompatibleInterfacesComparer<ICollection>
    {
        public const string IdentifiableName = "CollectionsComparer";
        
        public IObjectComparer ElementComparer { get; set; }

        public CollectionsComparer() : base("ICollection`1")
        {            
        }

        public CollectionsComparer(IObjectComparer elementComparer) : this()
        {
            ElementComparer = elementComparer;
        }
                
        public override string Name
        {
            get { return IdentifiableName; }
        }

        public Predicate<long> IgnoreElementAt { get; set; }

        private IResultBuilder CompareByCount(int leftCount, int rightCount, ICompareContext context)
        {
            if (leftCount < rightCount)
            {
                return this.IsNotEquals(context).
                    Message("<{LPath}>.Count is lesser than <{RPath}>.Count [{LCount} != {RCount}].").
                    With(new {LCount = leftCount, RCount = rightCount});
            }
            
            if (leftCount > rightCount)
            {
                return this.IsNotEquals(context).
                    Message("<{LPath}>.Count is greater than <{RPath}>.Count [{LCount} != {RCount}].").
                    With(new {LCount = leftCount, RCount = rightCount});                
            }
            return null;
        }

        private IResultBuilder CompareByEnumerators(IEnumerator enumerator1, IEnumerator enumerator2, int collCount, ICompareContext context)
        {
            long index = 0;
            var results = new List<IResultProvider>(collCount);
            while (enumerator1.MoveNext() && enumerator2.MoveNext())
            {                
                if (IgnoreElementAt == null || !IgnoreElementAt(index))
                {
                    var indexedContext = context.ForIndexedObjects(enumerator1.Current, enumerator2.Current, index);
                    if (ElementComparer.CompareAndAddResult(indexedContext, results).GetStatus() == CompareStatus.NotEquals)
                    {
                        return this.IsNotEquals(context, results).
                            Message("<{LElementPath}> not equals to <{RElementPath}> [{LElementValue} != {RElementValue}].").
                            With(new
                            {
                                LElementPath = indexedContext.LPath, 
                                RElementPath = indexedContext.RPath, 
                                LElementValue = indexedContext.LValue,
                                RElementValue = indexedContext.RValue
                            });
                    }
                }
                index++;                
            }
            return this.IsEquals(context, results).Message("All <{LPath}> and <{RPath}> elements are equals.");
        }

        public override IResultBuilder CompareVia(Type interfaceType, ICompareContext context)
        {
            object leftObj = context.LValue, rightObj = context.RValue;

            const string countProperty = "Count";
            int leftCount = leftObj.GetProperty<int>(interfaceType, countProperty),
                rightCount = rightObj.GetProperty<int>(interfaceType, countProperty);
            
            var countResult = CompareByCount(leftCount, rightCount, context);
            if (countResult != null)
            {
                return countResult;
            }
            
            var enumerableInterface = interfaceType.GetInterface("IEnumerable`1");
            
            const string getEnumeratorMethod = "GetEnumerator";
            IEnumerator enumerator1 = leftObj.InvokeMethod<IEnumerator>(enumerableInterface, getEnumeratorMethod),
                        enumerator2 = rightObj.InvokeMethod<IEnumerator>(enumerableInterface, getEnumeratorMethod);
                        
            return CompareByEnumerators(enumerator1, enumerator2, leftCount, context);            
        }

        public override IResultBuilder CompareVia(ICollection leftColl, ICollection rightColl, ICompareContext context)
        {
            var countResult = CompareByCount(leftColl.Count, rightColl.Count, context);
            if (countResult != null)
            {
                return countResult;
            }                                                          
            return CompareByEnumerators(leftColl.GetEnumerator(), rightColl.GetEnumerator(), leftColl.Count, context);
        }

    }
}